/**
 * 2025/01/01 - 2025/12/31
 */
package com.snail.starter.iam.initializing.local.adapter;

import cn.hutool.core.util.SerializeUtil;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.snail.starter.db.holder.DynamicTransactionTemplate;
import com.snail.starter.iam.initializing.IMenuAdapter;
import com.snail.starter.iam.entity.LocalApp;
import com.snail.starter.iam.entity.LocalMenu;
import com.snail.starter.iam.initializing.local.mapping.LocalMenuMapping;
import com.snail.starter.iam.initializing.properties.MenuYaml;
import com.snail.starter.iam.mapper.LocalAppMapper;
import com.snail.starter.iam.mapper.LocalMenuMapper;
import com.snail.starter.utils.id.ObjectId;
import com.snail.starter.utils.lambda.GenericEntityBuilder;
import com.snail.starter.utils.tools.CryUtils;
import com.snail.starter.utils.tree.TreeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Stream;

/**
 * @author zgadmin
 * @date 2025/8/27 10:55
 */
@Slf4j
@Component
public class LocalMenuAdapter extends ServiceImpl<LocalMenuMapper, LocalMenu> implements IMenuAdapter {

    @Resource
    DynamicTransactionTemplate dynamicTransactionTemplate;

    @Resource
    LocalAppMapper localAppMapper;

    @Resource
    LocalMenuMapping localMenuMapping;

    @Override
    public boolean init(MenuYaml menuYaml) {
        menuYaml.forEach(appNode -> {
            List<LocalMenu> appMenus = toLocalMenus(appNode);
            String menuMd5 = CryUtils.md5(SerializeUtil.serialize(appMenus), "app");
            final LocalApp app = toLocalApp(appNode, menuMd5);
            dynamicTransactionTemplate.doTransactionWithThrowable(() -> {
                LocalApp dbApp;
                try {
                    localAppMapper.insert(app);
                    dbApp = app;
                } catch (DuplicateKeyException duplicateKeyException) {
                    dbApp = localAppMapper.selectOne(Wrappers.lambdaQuery(LocalApp.class).eq(LocalApp::getAppKey, appNode.getAppKey()));
                    if (null == dbApp) {
                        throw duplicateKeyException;
                    }
                }
                if (menuMd5.equals(dbApp.getMenuSign())) {
                    return;
                }
                insertMenus(dbApp.getId(), appMenus);
                localAppMapper.updateById(GenericEntityBuilder.of(LocalApp::new)
                        .with(LocalApp::setId, dbApp.getId()).with(LocalApp::setMenuSign, menuMd5).build());
            });
        });
        return true;
    }

    private void insertMenus(Long appId, List<LocalMenu> appMenus) {
        if (!dynamicTransactionTemplate.doTransaction(status -> executeBatch(appMenus, appMenus.size(),
                (sqlSession, entity) -> {
                    entity.setAppId(appId);
                    sqlSession.insert(getSqlStatement(SqlMethod.INSERT_ONE), entity);
                })
        )) {
            log.warn("app-id:{}, load menu error:{}", appId, appMenus);
        }
    }

    private List<LocalMenu> toLocalMenus(MenuYaml.AppNode appNode) {
        return TreeUtil.buildList("0", appNode.getMenus(),
                MenuYaml.MenuNode::getPath,
                (menuNode, parentId) -> localMenuMapping.toLocalMenu(parentId, menuNode),
                menuNode -> null == menuNode.getChildren() || menuNode.getChildren().isEmpty() ? Stream.empty() : menuNode.getChildren().stream());
    }

    private LocalApp toLocalApp(MenuYaml.AppNode appNode, String menuMd5) {
        return GenericEntityBuilder.of(LocalApp::new)
                .withSupplier(LocalApp::setAppId, ObjectId::nextLongId)
                .withSupplier(LocalApp::setAppKey, appNode::getAppKey)
                .withSupplier(LocalApp::setName, appNode::getName)
                .with(LocalApp::setMenuSign, menuMd5)
                .with(LocalApp::setResourceSign, CryUtils.md5("", "resource"))
                .withSupplier(LocalApp::setUserType, appNode::getUserType)
                .withSupplier(LocalApp::setStatus, appNode::getStatus)
                .build();
    }

}
